{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4a420628-7ce6-4333-8dc7-fdd6608033d9",
   "metadata": {},
   "source": [
    "# 14.3 案例实战 - 电影智能推荐系统"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "24601b8b-9e3c-41c0-8db4-209ba41eed67",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "============================================================\n",
      "电影推荐系统 - 修复数据稀疏性问题\n",
      "============================================================\n",
      "\n",
      "=== 步骤1：数据加载和基础处理 ===\n",
      "电影总数: 9687\n",
      "平均每部电影评分次数: 10.4\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "from sklearn.metrics.pairwise import cosine_similarity\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "# 设置中文显示\n",
    "plt.rcParams['font.sans-serif'] = ['SimHei']\n",
    "plt.rcParams['axes.unicode_minus'] = False\n",
    "\n",
    "print(\"=\"*60)\n",
    "print(\"电影推荐系统 - 修复数据稀疏性问题\")\n",
    "print(\"=\"*60)\n",
    "\n",
    "# ==================== 继续您的代码 ====================\n",
    "print(\"\\n=== 步骤1：数据加载和基础处理 ===\")\n",
    "\n",
    "# 读取数据\n",
    "movies = pd.read_excel('电影.xlsx')\n",
    "score = pd.read_excel('评分.xlsx')\n",
    "df = pd.merge(movies, score, on='电影编号')\n",
    "\n",
    "# 计算电影统计信息\n",
    "ratings = pd.DataFrame(df.groupby('名称')['评分'].mean())\n",
    "ratings['评分次数'] = df.groupby('名称')['评分'].count()\n",
    "\n",
    "print(f\"电影总数: {len(ratings)}\")\n",
    "print(f\"平均每部电影评分次数: {ratings['评分次数'].mean():.1f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d6fb3f7-ded6-4507-bef9-f43bebb119ad",
   "metadata": {},
   "source": [
    "## 解决数据稀疏性问题"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "4221640e-1942-4612-ae14-74a946af2e71",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== 步骤2：筛选热门电影和活跃用户 ===\n",
      "热门电影数量 (≥50次评分): 450\n",
      "活跃用户数量 (≥50次评分): 384\n",
      "筛选后数据量: 37093 (原始: 100721)\n"
     ]
    }
   ],
   "source": [
    "print(\"\\n=== 步骤2：筛选热门电影和活跃用户 ===\")\n",
    "\n",
    "# 筛选评分次数较多的电影（至少50次评分）\n",
    "popular_movies = ratings[ratings['评分次数'] >= 50].index\n",
    "print(f\"热门电影数量 (≥50次评分): {len(popular_movies)}\")\n",
    "\n",
    "# 筛选评分次数较多的用户（至少50次评分）\n",
    "user_rating_counts = df.groupby('用户编号')['评分'].count()\n",
    "active_users = user_rating_counts[user_rating_counts >= 50].index\n",
    "print(f\"活跃用户数量 (≥50次评分): {len(active_users)}\")\n",
    "\n",
    "# 使用筛选后的数据\n",
    "filtered_df = df[\n",
    "    (df['名称'].isin(popular_movies)) & \n",
    "    (df['用户编号'].isin(active_users))\n",
    "]\n",
    "print(f\"筛选后数据量: {len(filtered_df)} (原始: {len(df)})\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32451f76-9219-4e9f-abc2-4a665c3f737d",
   "metadata": {},
   "source": [
    "## 创建用户——电影矩阵"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "31399ab7-3b4a-449a-83a0-58b4678354f5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== 步骤3：构建高质量的用户-电影矩阵 ===\n",
      "筛选后矩阵形状: (384, 450)\n",
      "新矩阵稀疏度: 78.54% (原来: 98.30%)\n"
     ]
    }
   ],
   "source": [
    "print(\"\\n=== 步骤3：构建高质量的用户-电影矩阵 ===\")\n",
    "\n",
    "user_movie_filtered = filtered_df.pivot_table(\n",
    "    index='用户编号', \n",
    "    columns='名称', \n",
    "    values='评分'\n",
    ")\n",
    "\n",
    "print(f\"筛选后矩阵形状: {user_movie_filtered.shape}\")\n",
    "\n",
    "# 计算新的稀疏度\n",
    "total_cells = user_movie_filtered.shape[0] * user_movie_filtered.shape[1]\n",
    "rated_cells = user_movie_filtered.notna().sum().sum()\n",
    "sparsity = (1 - rated_cells / total_cells) * 100\n",
    "print(f\"新矩阵稀疏度: {sparsity:.2f}% (原来: 98.30%)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53d71437-0fbe-4b72-a1f9-65f8afeac288",
   "metadata": {},
   "source": [
    "## 相似度计算"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "814b6dad-fef1-4a82-bc06-b83fce4f12b4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== 步骤4：改进的相似度计算 ===\n",
      "\n",
      "分析电影: 阿甘正传（1994）\n",
      "\n",
      "与'阿甘正传（1994）'最相似的电影 (至少20个共同评分用户):\n",
      "                                相关系数  共同用户数\n",
      "生命因你动听（1995）                0.638218     53\n",
      "Pocahontas（1995）            0.557049     49\n",
      "斗气老顽童2（1995）                0.534682     36\n",
      "几个好男人，A（1992）               0.533262     44\n",
      "Caddyshack（1980）            0.532464     42\n",
      "Wallace＆Gromit：错误的长裤（1993）  0.531786     43\n",
      "梦之场（1989）                   0.503845     43\n",
      "大（1988）                     0.492351     71\n",
      "胡克（1991）                    0.484676     47\n",
      "秋天的传说（1994）                 0.478226     44\n"
     ]
    },
    {
     "data": {
      "image/png": "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NbVQcQEA3Fvbtm3Nww8/bLOuVatWpl+/fnnu36ZNG9OrV688v2PyenTr1s088MADucqZPXu28fHxMXv37jVZWVk221544YVcSZ38vo+//fbbXAmUb775xnh7e5v169cXWPd69eqZt956y2ZdeHi4mTNnjs26vn37mh49ehRY1qVWr15tpkyZUmAfzBhjzp07Zxo1amTmzp1runfvbkaPHm3++9//WhNgn3/+ualQoUKhf2S67bbbzO23326zrlmzZqZDhw75xvL7779bE2A5bda5c2dTt27dPNszMTHR7N69u8R/6AY8lW8JDroCSrVDhw4pIyNDLVu2LHC/Cxcu6Pz58zp//rx8fX0VFhYmSapXr57CwsJUr149m/29vS9eBWuM0YYNGzR06FCFhobmW76Pj4/KlSunihUrSrp4+ZO3t7f8/f2LVJ8LFy4oKytLZcqUkSR99913ee73n//8R926ddPrr79+xTL37Nmjr7/+Wm+//bZWr16te+65x2Z75cqVbZYffPBBffrpp0WKe+/evRo5cqTWrl2rmjVr5tp+6NAh3XvvvUpLS8u17ZVXXtG7776ryZMn64YbbtA777yjtm3b6pdfftFNN92k33//XW3atNGjjz6q119/Xb/99ptiYmKUnJysN998M1d5ixYt0rJly9S3b99c27p3767XX39d0dHR+vXXX4vcPmFhYQoODrZ5/+TYunWrtczk5GRt3rxZH330kQIDA+Xn56egoKBcr03FihXl5+dns+7o0aNKSUmRn5+fHnzwQR0/flxeXl7av3+/PvvsMwUEBKhz585KSkpSRkaGypYtq2rVqtmUUbduXev/Z8+erbffflsLFy5UdHS0pIuXwQ0ZMkSjR4/WU089ZXNsQkKCduzYoWeffdY6RP+zzz5T48aNdejQISUmJuqHH37QzJkzdfr0aUkXPyfZ2dkKDw/P9ZqNHDlSdevWVb9+/azrGjVqpKeeekpDhgxRy5Ytc70u33zzjR555JE82yA2Ntbm0ojLrV27Vm3atMl3OwDAPWVnZysjI0P+/v7y8vKSt7e3fHx8bPa5dF3OtAs5fa6cvlxERISysrLk5+cnLy+vXM9z4cIF+fn5qWzZsvL1zf1n12OPPaamTZuqbt26+vPPP/Xpp59q9OjRufYzxuihhx5S79691bNnT5ttWVlZGjVqlNq3b68OHTpY13fp0kWdO3dWv379tHXr1nz7pnnFlZ/LX6McDzzwgBYvXpzntmHDhuVad+DAAVWvXl2S9MEHH+i+++5TWlqaDhw4oOzsbDVu3Fj16tXTt99+q++//1633367zaWDFotFFStWzBXPsmXL8py6YMaMGWrRooVmzJihoUOH5orn1ltvVVZWVp7x16pVK8/1ktS3b18u7wNKgpOTYoDD9OvXz/Ts2bPIxyUlJZnAwEBjjDGvvfZarlEfL7zwgunbt6/ZsmWLKV++vPWXnHr16tkMeW7YsKExxpjmzZvb/PrUs2fPIg8Bz3l07dr1ivE3adLEvPbaa4Wqa+/evY0k89dff5mMjAxrXV588UVz99132+ybmZlp0tPTC1XupR599NF849m3b5+pUaOGadq0qbn89HTmzBkTGBho82teZmamue6668zjjz9ujDEmOjra/Oc//7E5btSoUSYoKCjXL2zHjh0zFStWNMHBwXmOlMpx2223mU8++aQINbyob9++Zty4ccYYYySZf//917rtk08+MXfccYe5/fbbjSRzww03mDZt2piHHnrIDB06NNclBMYYM2fOHFOnTh2bda+88kqR3i95DfvPzs42GzZsMI888oipXr26dZSgxWIxQ4cONZJM586drSOwLhUXF2c6depkunTpYry8vKz73nPPPaZbt25m8uTJecbRvn37XGUtXLjQSDIrVqzItS0lJcXUrVvX1KtXzxw6dMhm24ULF0xaWpr11+M9e/aYgIAAM2/ePGOMMePGjcv3UlUAgGfatm1bkftc4eHh1uPbt29v+vbta9auXXvF45KSksyDDz5o+vTpY4y5eOl7XqN+ckYL50wdcOlIqfXr1xtJZvny5bmOi42NNb6+vmbHjh25th08eNBcc801pmXLlubMmTN5vhaRkZGFHimVV//EmIv9x7vvvvuKVyPExsYaSTYj7Tt16mS6du1qqlevbiSZ+vXrm3vvvdf06NHDTJw40UREROT5uh44cMAmhpMnT5rKlSvnOSLNGGNGjhxpfHx88hy5fvbsWZORkWEdSdWmTRtrX2XlypXmww8/zLNMACWDpBQ8xh133GGee+65Ei83Jyk1YsQIm2HNkZGRZuLEiSYpKcmMHz/eNG7c2BiTOyl15MgRc/DgQXPkyBEzZ84cExQUZI4cOZLnIzg42EyePNkcOXLE7N+/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qn79+um2226z2VaUflx+srKydObMGe3Zs6fA/XJeD2OMdXnr1q364YcfFBYWpipVqqhZs2Z65ZVX9Nlnn2nw4MG69tprtW3bNjVu3FhHjhzJNcfT999/r6lTp+r999/Xp59+qieffLLAGD799FO1b99eX3zxha677jrr+n379ql79+5KS0vT66+/nusH14iICJvldevWqXXr1gU+F4DCISkFj1C+fHnrF+CV3HjjjQVOapgfb29vvfLKK3r44Yc1duzYXEmpwj7/uXPn8pwss7AxSMp1/NChQ/Xcc89Zl7Oysmx+mRs0aJDq16+vSpUqWcvJ2R4XF6eaNWvq3LlzNpNMShe/oIsyWqdMmTI6ceJEkep06bGzZ89W7969VblyZfn5+encuXOqVauWunbtKkl6/fXX9eOPP6pVq1YKDQ1VcnKyJNkkJIYOHWqdPLswTpw4IV9f31yJnKv1ww8/KDExUVWrVlXfvn21Zs0a9e/fX9dff72ysrJy/RppsVhslr/77jvdd9991klac2RkZCgrKyvf99Dlv4bu2rVL33//vaZNm6bs7GytXr1abdq00fnz53X69GmdO3dO48ePV8+ePdW9e3etX79et9xyiySpc+fOatGihY4dO6b+/fvrwQcfVHZ2tmrXrq2QkBBNnDhRo0aN0saNG9WmTRvt3btXDRs2tIln9uzZOnLkiGrUqKG0tDQFBASoZcuWuvnmm/Xee+9Z93vjjTc0f/58lS1bNtekouPHj9fIkSP1+OOPa9asWTpx4oQqVKgg6WICasKECXrggQckXUwAzpgxoxAtBABwR9nZ2crMzCzyDUyys7N14cKFXCPRc36w2rx5s2644QZJF0dkT5gwoUjlV61aNVc/S8rdp/v666/VrVs3LV++XOfPn1fFihV1/vx5+fv7q3r16ho6dKjNSKjHHntMBw4cUMWKFfW///0vV/mZmZnW7/wcxhilpaXZrLtw4UK+sZ86dUpff/21vvrqq0LVNTMz01rnCRMmKDAwUJMnT9aqVav00ksvKSUlRdWqVdMjjzyi6tWra/369Xrssce0e/fuXP2IDh06aM+ePbrhhhv08MMPy8/PTx9++KGef/55nT171vr65SQPIyIi1KJFCz300EPWMn777Td17dpVderUUXBwsB577DE9++yzki5eEbBlyxZ98803ki72x8qVK8coKaAEkZQCLnP48OE8f/EqjNtuu03nz5/Xrl27ZIxR//791b9/f0m64q9YOYKCgrRjx448tzVu3LhYcYWEhOR5p7sc/v7+6tChQ66hydLFu5vt378/V6KuYcOG2rlzZ5HiqF+/vpKSknT27FmVK1euSMdKUq9evXTgwAHFxcXpl19+0VtvvaXRo0dbL0urXLmytm/frri4OB09elRDhgxRkyZNrJedffPNN1q2bJl27txZ6MTf77//rrp165b4SLdx48bpvvvuU0JCgvz8/DR79myNHDlSFotFX331lcqXL5/rmDp16lj/36pVK+uliZfGFhMTo08++UT9+vWzjhyS/u/OQZfffWfQoEH66aefJF28Y+A999yjs2fPKjMzUwEBASpfvrzKlSuna665RkePHlXnzp31008/qXbt2po8ebJ1VGBmZqY++OADffDBBzp//ryefPJJvf7669qwYYO+/PJLhYeHKzMzM9d72N/f3/pLZZkyZZSdna3du3fr4Ycfttnv0KFD1jv1XComJkaTJk3ShAkT1LlzZ82aNUs+Pj42lyp6e3tbly/9PwDA8/zxxx+qX79+ntsuv3R+/fr1+vDDD63Lt99+u3788UebfXL6E+XLl7eOcg4KCsp32ob87NixQ35+ftZk2dixY7V9+3YtXbpU0v/dMTDn7s3BwcEKDg6W9H/TFxw5ckQ33XSTTbn5fX/mSE9P10svvaSXXnrJZv3gwYM1ePBgm3W9evXKs4zjx49rwIABNq9VXjZu3KhXX33V+prt27dPN998swICAnT+/HmVKVNGN910kzIzM1WmTBkdO3ZMv/76q7p3764LFy7op59+0osvvpir3JxkYE7CMD4+Xg0bNrTp6x06dEiScr0WW7duVcuWLXXrrbfqm2++Ufv27W36Cl5eXvLy8rIu5yTU6EsAJYdPE9zenj17rJei5ZeIsFgsKl++vCpWrKizZ8/mGqJbGP/884/eeusteXt7q3r16srKytI777yj3r17a/78+Zo9e3ahyvHy8tL111+f5zZHXAJ4uVWrVtksHzt2TDVr1tTIkSOLPKKratWqql+/vubPn1/okUqXq1Spkvr06aMFCxaoXr16evTRR222+/r6qnPnzvr888917tw5xcbGWrctXrxYqampeSboPv74YyUlJeXaNnv2bHXr1q1YseZn2bJlWrVqlTZv3qzHHntM0sURekuXLtVzzz2n7t2766OPPrI55tNPP9W0adOsy0FBQTbDziXp77//1uLFi/XYY49pzpw56tmzZ67L5S43e/Zs6y+t5cuXV1BQkI4dO6aIiAjt2bPH5vX466+/NGHCBGtia+LEiZo4caJ69eql2rVra+LEiUpNTVW1atX04IMPSpIefvhh9ejRQxkZGWrTps0VR5ytW7dOp0+fVtu2bW3WHzp0KM/P5YgRI9S0aVM98MADio+PL7BsAADq1Kmjw4cPKzAw0KYfY4wpcDkrKyvPeZNy1qWmplpHFqWlpSkjI6PQMa1du1a7du2yGcmcc2lfYedAXLZsmfz8/HJdCn/o0KFcl7Vf6sSJE1q4cKGio6Ot64oyp1RWVpbi4+N13333XTHGFi1a6Pvvv7cuN2rUSFlZWfr666/19NNPa9++fQoICFC/fv3k7e0tPz8/3XbbbQoNDdWrr76qnTt35uof5BXP119/bR0hnePQoUPy9vbONa9pkyZN9MEHH+jee+9V2bJlr1gHACWPpBTc3tNPP62NGzcqICAg3yRKVlaW7r33Xutw5+IkpTp06KDExES98cYb1pFWOb+a3X333UWeCLM0WbRokTp06KBrrrlGU6dOVbVq1WyGPRfFCy+8oOHDh+v++++3/tpXVNu2bdPKlSv11Vdf5flLpDFG48aN0913363bb7/dun7s2LE2lzFK0quvvmrddnlHZe3atfruu+80derUYsWZHz8/Pz399NNq0qRJntv9/f1zvTZXGll28uRJdenSRU2aNNGsWbN0zTXX6P7779f06dP19NNP53tcvXr19PXXX2v27NmaOHFirssZsrOz9dRTT6l169bq3bu3Zs6cabN9+/bt+vLLL9W3b18tX75cP/zwgyIjI61zT91xxx26/vrr9f7772v58uUF1iErK0uvvPKKbrvttly/Yh86dMg6B8elwsPDrR3PwsyLURJzZwAAXJefn5+qVKliXTbG6Pnnn9eePXs0b948VapUScnJyWrXrp169Oihl19+ucBLtXIur798rsOC5tC81O+//67Jkydbf8wpjrNnz+rNN9/UfffdZx09lSO/H3Vytp09ezbfkWOFsW7dOp09e/aK80Lmxxij1157TWFhYfr4449Vo0YNffbZZ9q+fbukiz/WDh06VM8++6zuvffefG+Wk2PmzJn6559/rFcq5Dh06JCqVKmS5winRx55xPr/K/UT6EcAJY+kFNzemjVrCr3vt99+Ky8vL1WrVq3IzzNr1ixVr17deolVzpepdDHJVZhEV3Z2towx2r9/f4Hb7SWn7Euf48yZM5o4caIGDBigPn36aPHixZoxY0aRh6Xn6NOnj+bNm6fevXtr+fLlBd4lMD8vv/yy7rjjDvXs2TPP7Z9//rl27dqlzz77zGZ9jRo1cl2aGRYWJknWeZJyJCUlqU+fPhozZkyuEUlXq0uXLrrrrrvy3FZQZydnyPjl1q1bp0GDBikoKEj/+9//5OXlpdjYWKWmpmrw4MFasGCB3njjDbVt2zZXYvbnn3/WQw89pE6dOun8+fO5JlTPyspSYGCg+vTpo/Xr12vGjBk2nfObb75ZW7duVVxcnF544QXt27dPN910k5YsWaL77rvP5k6Q6enp+dYtPT1dDz/8sLZt26aNGzfmqndiYuIVP0OXz7t1qcTERM2fP19Lly7NNWE/AMAzHTlyRP3799d3332nJ5980jqa1xijli1b6s0339SiRYs0e/Zsmxt9XCoiIkLHjx/Ptf5Ko9uNMTp69KjWr1+vvn376t133y1WHZKTk9WjRw+dPn1a//3vf222HT9+XMnJyfl+f65du1YVKlS4qh9OJ0+erPr16+dKyhWWl5eXVqxYobi4OC1dulRfffWVgoKCtGjRIg0dOlShoaHWfmlWVpYyMzPzvXRuwYIFGjZsmIYNG6Z69erZbNu7d2+h+uIF9SWmTp2qrVu3ShJ9CaAEOf5aIKAU27lzp6699toiTX6Zk0Ro3bq1zZw/+UlOTi5wxFZaWppq1aqV5yM1NTXPiSZ37dqlXbt2We9qsmfPHusjPT1dJ06csFm3a9cubdu2TQcOHLApJ2eo+aXPUaFCBf32229auHChNm7cqJSUFK1atSrP+acKw8vLS59++qmOHTumjh075nt74fysX79e3333nSZPnpzn9szMTI0aNUqDBg1S3bp1ixVjfHy8WrRooW7duuU5d0FJyOmsZmZm2iSbsrKy9MUXX1jnMMh59O/f32a/jIwMLV++XHfffbfatWundu3a6ccff7ReWufl5aW3335b7733nnbu3Kn27durWrVqmj9/vrWMzz77TB06dFD//v31xRdfWG+9nPM8OXfqmz59uj799FPNnTtXbdq0sZn41MvLSw0aNJCPj4+OHTum2bNn69FHH9Xy5cu1e/dutWjRQj4+Pho+fLh69+6tefPm5Xot4uLi1KpVK33zzTf67LPPrPNOHTx4UKNGjVLPnj2VkpJS4OUHUt4dyZxEbvXq1TV58mRlZ2dr1KhRBZYDAHBvZ86c0ahRo3TDDTfo999/14oVK/Tee+9Zv0PDwsL01ltv6eeff5aPj4/atGmjmJiYPH8c8vb2VsWKFXM9cn70kpTnZX9r167V3r17FR0drY8++ihXEutKP0JmZ2dryZIlat68uXbt2qUVK1ZYky47d+7UK6+8onvvvVe+vr75jsz++OOP1aNHj1zPnZ2dnetHspSUlFyX4C9evFgrV65UTExMsW/SI12cE7RmzZrau3evHnjgAS1atEibNm3SkSNH9Nxzz+m1117T+++/rzVr1uiBBx7INSH8gQMH9Mwzz+iRRx7R/fffr/Hjx1u3TZ8+XUOHDtXcuXOv2I+QcvclLv1BOD4+Xt99951eeumlEv/BEvBoBvBwP/zwg3n66afNI488YsqVK2ceeuihIh0/ZMgQ07dv3yvu9/LLL5sGDRoYSWb58uV57vP555+b6667Lt8yKlSoYKZMmZJr/S233GLKly9vKlSoUKhHcHCwKVOmjImJibEpZ+vWrUaS+f333/N8/szMTDNz5kwTHh5u7rzzzivWuSDJycnmjTfeMFlZWVdVjj0cOXLETJo0yWRnZ9v9uerUqWNGjBhhXX7sscdM586dTVJSks1j4sSJplKlStb9du7caSpUqGDatWtnNmzYUOBzHDx40AwbNszUrFnTHDt2zBhjzKlTp0z9+vXNq6++at3v8OHDZsiQIaZ9+/ZGkjl16pRNOStWrDAPPfSQuXDhgjHGmPj4ePP000+bihUrmvbt25v4+HhjjDHnzp0zQ4cONf7+/qZ///4mPT3dGGPM888/bySZwYMHW8tMSEgwoaGhpnnz5mbbtm02z5ednW0aNWpkmjRpYj766KPCvqQ2KlWqZObNm2eMMda4AQCebcyYMcbb29sMGDDAnDx5ssB9z507Zx5//HFToUIFs2vXLmOMMbfddluh+n6ff/65ueeee0xgYKAZMGBAru2TJ0/O97vpmWeeMa1atcq37PXr1xt/f39z1113mcTERJttaWlp5tprrzW33357vn3On376yUgya9asybWtbNmy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fI4co7jtFBsbq7p16+rBBx+0d4j4/xWnrTZt2qTFixerevXqSkxMVN++ffXMM884Ily4mbVr12r16tWaMGGCdZ27fr+ePXtWr732mmrXrq2///5bU6dOVcuWLa/6e600atCgge677z7deuutCgkJ0aZNmzRp0iS3quul9chRUP1c+X2dV10vdeHCBU2ePFlPPvmkpKvvqzlbfvXN63wlufY5K6+65neuOn/+vFu2bX7nK8n138s5Lv2M2vM85VuiUaPY3n//fZ0/fz7X+unTp1szjjkCAwOVlpam0NDQK5abnZ2tAQMGaODAgWrYsGGJxYuLfH19FRAQYLPuSu1z+TE5+8O+itNWOfgcOU5x2mn37t2aOXOmtm3b5ogQ8f8rTlvNmjVLzZs31//+9z95eXnp8ccf13XXXachQ4boxhtvdETYcAPp6ekaNGiQ3nvvPZUvX9663l2/X5cuXapz585p7dq1qlixojIzM3XTTTfpo48+Uv/+/W32LUofsTTavHmzvv76a/3888+qV6+eJkyYoC5duqhdu3bF/g53BQWdT931fS1Jr732msqWLWudU+pq+mqlVX7nK8n9zln5navmz5/vlm2b3/lq8+bNblPfSz+jo0aNstt5ipFSpcS1116rmjVr5npUrlxZx48ft9k3NTVV/v7+hSr3jTfe0KlTpzRx4kR7hO3xwsLCitw+lx9TlPZE8RWnrXLwOXKcoraTMUZPPPGE3nzzTVWtWtURIeL/V5zP1MGDB9WlSxfrjy0RERG65pprlJiYaNdY4V7eeOMNNW3aVF27drVZ767frwcPHlRUVJQqVqwo6eIfspGRkTp9+vRV9RFLo0WLFumhhx5S8+bNVaFCBb355ptKTEy8qu9wV1BQ/dz1fb1mzRq98847Wrhwofz8/CRdXV+ttMrvfCW53zkrv3PVvn373LJt8ztf7dixwy3qe/ln1J7nKZJSpVzTpk21adMm63JSUpIsFovCwsKueOzXX3+tKVOm6IsvvrBe+4mSVZz2ufyYbdu2qVq1anaNE8X/LPE5cqyittM///yjH3/8UTExMdbLnv/55x9FRkZq4cKFjgrbIxXnM1W9enWbUcFnz57VqVOnOAeiSBYuXKhly5ZZP/MLFy7U008/rY8//tgtv18v/9xI0t9//61p06YVu49YWmVnZ+vYsWPW5dTUVOuv8O5W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kTPyKqc+zzzzjNasWaO1a9fKz89PPXr00Pz585Wenq5nn33W2pdKSkqiLwV4EC7fA1AqjB8/Xn379tXs2bP1zDPPyNvbW8nJyfr0009tft3LyMjQrbfeKkn68ccfbcr4/fff1aRJE9WoUUO//vqrzd1qLlWzZk0tXbpU11xzjYKDg1WmTBl98skn2rp1qz7++GP5+PjIy8tLn332mU6ePClJeueddzR9+nSdOnVKwcHBqlOnjo4dO6bXX39dTz75pOrXr6/Tp09r7969uvnmm+30KgEAAHe2atUqPfHEE7kujbv8cr2aNWtKutgv2rJli822y5NU+a2TLiaWJCkxMdE691NhL987duyYPvnkE0VHR+dZdnZ2tp5++mlrf2z06NH66quv9O+//6pSpUqqWbOmDh8+rE8++USRkZF68MEH1b9/fwUFBRV4Fz8A7oWRUgCcbv/+/frss880YsQI67oJEyaoevXqeuihh3TixAnr47PPPsuzDIvFoiFDhmj48OFq2LCh7r//fqWmpub7nNWrV1e/fv2UkpKiSpUqKSgoSH5+fvL19VV0dLRGjBihXr166YEHHpAkvfTSS9q5c6euvfZa7d27V3v37lV4eLgeeughBQcHq1WrVurVq5datmzJXfcAAECxdO7cWQcOHLCOcvr111+1Zs0a6/K3336r7du3W5cPHTqkKlWq2JSRnp6uIUOGWEeMr1y5UikpKXk+386dO1W/fv0i913Onj2rhISEAn+I8/b21syZM1W5cmVJ0ttvv601a9bo+uuv199//61ffvlF4eHh6tq1q+rVq6cyZcpo4MCB6tKlS5FiAeDaSEoBcLqaNWtq48aN1k7LlWRnZ9ssHzlyRD179lSZMmU0atQoffjhh0pJSVHTpk21fPnyXMfv27dPnTt3Vs+ePdWoUaNc28eOHauVK1eqXbt2+vfff63rf/vtN1WqVEldu3ZVz549dfPNN6tGjRqSpCeffFJr1qxR//79i1J1AACAPO3cuVO33367HnjgAeuNXZ5//nndeOONWrBgQb7HLV26VKmpqdYf9CwWS76jmb766qtcE4obY2xGVmVlZeW6m9+nn36q6tWrq0GDBkWq05YtWxQYGKiOHTuqV69e6tWrl3UkFX0pwDORlAJQKtSuXVvSxYRTTtIpKytLCxcuVEhIiPVx3333WSct/+OPPzR69Gg1bNhQDRs21LJlyxQYGKjg4GCtWrVKd9xxh+6++27Vrl1bkyZNknRxeHrz5s01fPhwvfrqqzp37py+/fZbrVu3TmXKlJEk1a1bVxs2bJCXl5dmzZpljbFDhw7atGmTWrRooX///Vfnz5/Xk08+qX/++UcxMTFq3bq1Xn75ZR09etSRLx0AAHAjp06dUkxMjJo3b64uXbroxx9/VGBgoCTpm2++0ejRozVo0CB17dpVx44dk3RxbqmCpKSkaOXKlfrzzz+t80udPHlSc+bM0cMPP2yzb2Zmpi5cuCBJevXVV/XOO+/oxhtvtIlvzJgx1jsEFkXv3r21adMmValSRT4+Ptq8ebPGjx+vnTt3aty4cWrZsqWeffZZnT17tshlA3BNJKUAlCoWi0UWi0WSdOHCBfXu3VunT5+2PpYsWWLtKE2dOlXx8fH6/vvvNXnyZAUEBFjLCQoK0gcffKBNmzapWbNm1nmo6tSpo61bt+qJJ56QJJUpU0YvvPCCTpw4YfPLXE5ia9SoUZKkf/75R+PGjVNkZKTS09O1YcMGffPNN6pUqZKaN2+uZ555RuvWrVOrVq10yy23aN26dY54uQAAgJvp06ePVqxYoRUrVmjatGnWhJR08ZK4Z555Rtu2bdPx48c1Y8YMSf83N1R+/P39FR0dLW9vb912222SpOTkZA0ZMkSNGze22XfatGnWdbfeeqsmT55sk4CaMGGCgoODNXDgwCLVa/fu3Ro1apRuvvlm1a1bV99//72+++47HT58WHfddZemT5+utWvXqnz58rrlllvynMcKgPvxMvnNegcATnDw4EFde+218vPz09mzZ5WRkaHQ0FBnh6WUlBTNmDFDjz76qPWSPWOMpk+frnvvvde6TpI+//xz9ejRw6YTCQAAUBjnzp1TYGBgrsnOL5eZmWm9OUthZGVlXbHMwjp58qTCw8OLdMyBAwf06aefqn///taJzC0Wi2bMmGGzLjs7W59++qkeeuihAu8aCMA9kJQCAAAAAACAw5F6BgAAAAAAgMORlAIAAAAAAIDDkZQCAAAAAACAw5GUAgAAAAAAgMORlAIAAAAAAIDDkZQCAAAAAACAw5GUAgAAAAAAgMORlAIAAAAAAIDD/X8wswHYyzQkswAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 1200x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "print(\"\\n=== 步骤4：改进的相似度计算 ===\")\n",
    "\n",
    "def calculate_improved_similarity(movie_name, min_common_users=10):\n",
    "    \"\"\"改进的相似度计算，要求至少有min_common_users个共同评分用户\"\"\"\n",
    "    if movie_name not in user_movie_filtered.columns:\n",
    "        return pd.DataFrame()\n",
    "    \n",
    "    target_movie_ratings = user_movie_filtered[movie_name].dropna()\n",
    "    similarities = {}\n",
    "    \n",
    "    for other_movie in user_movie_filtered.columns:\n",
    "        if other_movie != movie_name:\n",
    "            other_movie_ratings = user_movie_filtered[other_movie].dropna()\n",
    "            \n",
    "            # 找到共同评分的用户\n",
    "            common_users = target_movie_ratings.index.intersection(other_movie_ratings.index)\n",
    "            \n",
    "            # 只有共同评分用户足够多才计算相似度\n",
    "            if len(common_users) >= min_common_users:\n",
    "                # 计算皮尔逊相关系数\n",
    "                correlation = np.corrcoef(\n",
    "                    target_movie_ratings[common_users], \n",
    "                    other_movie_ratings[common_users]\n",
    "                )[0, 1]\n",
    "                \n",
    "                if not np.isnan(correlation):\n",
    "                    similarities[other_movie] = {\n",
    "                        '相关系数': correlation,\n",
    "                        '共同用户数': len(common_users)\n",
    "                    }\n",
    "    \n",
    "    # 转换为DataFrame并排序\n",
    "    similarity_df = pd.DataFrame.from_dict(similarities, orient='index')\n",
    "    if len(similarity_df) > 0:\n",
    "        similarity_df = similarity_df.sort_values('相关系数', ascending=False)\n",
    "    \n",
    "    return similarity_df\n",
    "\n",
    "# 分析《阿甘正传》的相似电影\n",
    "target_movie = '阿甘正传（1994）'\n",
    "if target_movie in user_movie_filtered.columns:\n",
    "    print(f\"\\n分析电影: {target_movie}\")\n",
    "    \n",
    "    # 使用改进的相似度计算\n",
    "    improved_similarity = calculate_improved_similarity(target_movie, min_common_users=20)\n",
    "    \n",
    "    if len(improved_similarity) > 0:\n",
    "        print(f\"\\n与'{target_movie}'最相似的电影 (至少20个共同评分用户):\")\n",
    "        print(improved_similarity.head(10))\n",
    "        \n",
    "        # 可视化相似度分布\n",
    "        plt.figure(figsize=(12, 5))\n",
    "        \n",
    "        plt.subplot(1, 2, 1)\n",
    "        improved_similarity['相关系数'].hist(bins=20, color='lightcoral', alpha=0.7)\n",
    "        plt.title(f'与\"{target_movie}\"相关系数分布')\n",
    "        plt.xlabel('相关系数')\n",
    "        plt.ylabel('电影数量')\n",
    "        \n",
    "        plt.subplot(1, 2, 2)\n",
    "        improved_similarity['共同用户数'].hist(bins=20, color='lightblue', alpha=0.7)\n",
    "        plt.title('共同评分用户数分布')\n",
    "        plt.xlabel('共同用户数')\n",
    "        plt.ylabel('电影数量')\n",
    "        \n",
    "        plt.tight_layout()\n",
    "        plt.show()\n",
    "    else:\n",
    "        print(f\"没有找到与'{target_movie}'有足够共同评分用户的电影\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1297e89a-1226-4c92-81e1-6bd6110bdcbb",
   "metadata": {},
   "source": [
    "## 基于相似度的推荐"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "cb37bfed-9f2d-4115-928c-42b8535b81e7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== 步骤5：电影推荐系统 ===\n",
      "\n",
      "--- 用户 1 的推荐电影 ---\n",
      "用户 1 已评分电影数: 117\n",
      "1. Wallace＆Gromit：错误的长裤（1993）\n",
      "   预测评分: 4.34\n",
      "   实际平均评分: 56.00\n",
      "   评分次数: 56\n",
      "2. 华尔街之狼（2013）\n",
      "   预测评分: 4.32\n",
      "   实际平均评分: 54.00\n",
      "   评分次数: 54\n",
      "3. 模仿游戏（2014）\n",
      "   预测评分: 4.30\n",
      "   实际平均评分: 50.00\n",
      "   评分次数: 50\n",
      "4. 玩具总动员2（1999）\n",
      "   预测评分: 4.21\n",
      "   实际平均评分: 97.00\n",
      "   评分次数: 97\n",
      "5. Bug的生活，A（1998）\n",
      "   预测评分: 4.19\n",
      "   实际平均评分: 92.00\n",
      "   评分次数: 92\n",
      "\n",
      "--- 用户 4 的推荐电影 ---\n",
      "用户 4 已评分电影数: 84\n",
      "1. Sting，The（1973）\n",
      "   预测评分: 3.56\n",
      "   实际平均评分: 64.00\n",
      "   评分次数: 64\n",
      "2. 这是美好的生活（1946）\n",
      "   预测评分: 3.52\n",
      "   实际平均评分: 58.00\n",
      "   评分次数: 58\n",
      "3. 玩具总动员2（1999）\n",
      "   预测评分: 3.51\n",
      "   实际平均评分: 97.00\n",
      "   评分次数: 97\n",
      "4. 大（1988）\n",
      "   预测评分: 3.48\n",
      "   实际平均评分: 91.00\n",
      "   评分次数: 91\n",
      "5. 亲爱的，我缩小了孩子们（1989）\n",
      "   预测评分: 3.47\n",
      "   实际平均评分: 68.00\n",
      "   评分次数: 68\n",
      "\n",
      "--- 用户 6 的推荐电影 ---\n",
      "用户 6 已评分电影数: 93\n",
      "1. 华尔街之狼（2013）\n",
      "   预测评分: 3.99\n",
      "   实际平均评分: 54.00\n",
      "   评分次数: 54\n",
      "2. 敲门（2007）\n",
      "   预测评分: 3.87\n",
      "   实际平均评分: 52.00\n",
      "   评分次数: 52\n",
      "3. 角斗士（2000）\n",
      "   预测评分: 3.81\n",
      "   实际平均评分: 170.00\n",
      "   评分次数: 170\n",
      "4. Monsters，Inc。（2001）\n",
      "   预测评分: 3.81\n",
      "   实际平均评分: 132.00\n",
      "   评分次数: 132\n",
      "5. 爱国者，（2000）\n",
      "   预测评分: 3.79\n",
      "   实际平均评分: 68.00\n",
      "   评分次数: 68\n"
     ]
    }
   ],
   "source": [
    "print(\"\\n=== 步骤5：电影推荐系统 ===\")\n",
    "\n",
    "def recommend_movies(user_id, n_recommendations=10, min_common_users=15):\n",
    "    \"\"\"为用户推荐电影\"\"\"\n",
    "    if user_id not in user_movie_filtered.index:\n",
    "        print(f\"用户 {user_id} 不在活跃用户列表中\")\n",
    "        return pd.DataFrame()\n",
    "    \n",
    "    # 获取用户已评分的电影\n",
    "    user_ratings = user_movie_filtered.loc[user_id].dropna()\n",
    "    print(f\"用户 {user_id} 已评分电影数: {len(user_ratings)}\")\n",
    "    \n",
    "    # 计算候选电影的推荐分数\n",
    "    recommendations = {}\n",
    "    \n",
    "    for movie in user_movie_filtered.columns:\n",
    "        if movie not in user_ratings.index:  # 用户未评分的电影\n",
    "            weighted_sum = 0\n",
    "            similarity_sum = 0\n",
    "            \n",
    "            # 基于用户已评分的电影计算推荐分数\n",
    "            for rated_movie, rating in user_ratings.items():\n",
    "                # 计算两部电影之间的相似度\n",
    "                target_ratings = user_movie_filtered[rated_movie].dropna()\n",
    "                candidate_ratings = user_movie_filtered[movie].dropna()\n",
    "                \n",
    "                common_users = target_ratings.index.intersection(candidate_ratings.index)\n",
    "                \n",
    "                if len(common_users) >= min_common_users:\n",
    "                    correlation = np.corrcoef(\n",
    "                        target_ratings[common_users], \n",
    "                        candidate_ratings[common_users]\n",
    "                    )[0, 1]\n",
    "                    \n",
    "                    if not np.isnan(correlation):\n",
    "                        weighted_sum += correlation * rating\n",
    "                        similarity_sum += abs(correlation)\n",
    "            \n",
    "            if similarity_sum > 0:\n",
    "                predicted_rating = weighted_sum / similarity_sum\n",
    "                recommendations[movie] = predicted_rating\n",
    "    \n",
    "    # 排序并返回推荐结果\n",
    "    rec_df = pd.DataFrame.from_dict(recommendations, orient='index', columns=['预测评分'])\n",
    "    rec_df = rec_df.sort_values('预测评分', ascending=False).head(n_recommendations)\n",
    "    \n",
    "    return rec_df\n",
    "\n",
    "# 示例：为几个用户推荐电影\n",
    "sample_users = user_movie_filtered.index[:3]\n",
    "for user_id in sample_users:\n",
    "    print(f\"\\n--- 用户 {user_id} 的推荐电影 ---\")\n",
    "    recommendations = recommend_movies(user_id, 5)\n",
    "    \n",
    "    if len(recommendations) > 0:\n",
    "        for i, (movie, predicted_rating) in enumerate(recommendations['预测评分'].items(), 1):\n",
    "            actual_rating_count = ratings.loc[movie, '评分次数']\n",
    "            avg_rating = ratings.loc[movie, '评分次数']\n",
    "            print(f\"{i}. {movie}\")\n",
    "            print(f\"   预测评分: {predicted_rating:.2f}\")\n",
    "            print(f\"   实际平均评分: {ratings.loc[movie, '评分次数']:.2f}\")\n",
    "            print(f\"   评分次数: {actual_rating_count}\")\n",
    "    else:\n",
    "        print(\"无法生成推荐（数据不足）\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3754f060-02e5-4a22-89dd-8a38d25d7762",
   "metadata": {},
   "source": [
    "## 推荐算法评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d6b16c0b-cac1-4fff-8ccc-e385c1df0a84",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== 步骤5：推荐算法实现 ===\n"
     ]
    }
   ],
   "source": [
    "print(\"\\n=== 步骤5：推荐算法实现 ===\")\n",
    "\n",
    "class MovieRecommender:\n",
    "    def __init__(self, user_movie_matrix):\n",
    "        self.user_movie = user_movie_matrix\n",
    "        self.user_mean = user_movie_matrix.mean(axis=1)\n",
    "        \n",
    "    def item_based_recommend(self, user_id, n_recommendations=10):\n",
    "        \"\"\"基于物品的协同过滤推荐\"\"\"\n",
    "        if user_id not in self.user_movie.index:\n",
    "            return pd.DataFrame()\n",
    "        \n",
    "        # 获取用户已评分的电影\n",
    "        user_ratings = self.user_movie.loc[user_id].dropna()\n",
    "        \n",
    "        if len(user_ratings) == 0:\n",
    "            return pd.DataFrame()\n",
    "        \n",
    "        # 计算推荐分数\n",
    "        recommendations = pd.Series(dtype=float)\n",
    "        \n",
    "        for movie in self.user_movie.columns:\n",
    "            if movie not in user_ratings.index and pd.notna(self.user_movie.loc[user_id, movie]):\n",
    "                # 计算该电影与用户已评分电影的加权相似度\n",
    "                weighted_sum = 0\n",
    "                similarity_sum = 0\n",
    "                \n",
    "                for rated_movie, rating in user_ratings.items():\n",
    "                    if rated_movie in self.user_movie.columns:\n",
    "                        # 计算电影间相似度\n",
    "                        similarity = self.user_movie[rated_movie].corr(self.user_movie[movie])\n",
    "                        if pd.notna(similarity):\n",
    "                            weighted_sum += similarity * rating\n",
    "                            similarity_sum += abs(similarity)\n",
    "                \n",
    "                if similarity_sum > 0:\n",
    "                    predicted_rating = weighted_sum / similarity_sum\n",
    "                    recommendations[movie] = predicted_rating\n",
    "        \n",
    "        # 返回推荐结果\n",
    "        return recommendations.sort_values(ascending=False).head(n_recommendations)\n",
    "    \n",
    "    def user_based_recommend(self, user_id, n_recommendations=10):\n",
    "        \"\"\"基于用户的协同过滤推荐\"\"\"\n",
    "        if user_id not in self.user_movie.index:\n",
    "            return pd.DataFrame()\n",
    "        \n",
    "        # 找到相似用户\n",
    "        target_user_ratings = self.user_movie.loc[user_id]\n",
    "        similarities = self.user_movie.corrwith(target_user_ratings)\n",
    "        similar_users = similarities.dropna().sort_values(ascending=False)[1:11]  # 排除自己，取前10个相似用户\n",
    "        \n",
    "        recommendations = pd.Series(dtype=float)\n",
    "        \n",
    "        for movie in self.user_movie.columns:\n",
    "            if pd.isna(target_user_ratings[movie]):\n",
    "                weighted_sum = 0\n",
    "                similarity_sum = 0\n",
    "                \n",
    "                for similar_user_id, similarity in similar_users.items():\n",
    "                    if pd.notna(self.user_movie.loc[similar_user_id, movie]):\n",
    "                        weighted_sum += similarity * self.user_movie.loc[similar_user_id, movie]\n",
    "                        similarity_sum += abs(similarity)\n",
    "                \n",
    "                if similarity_sum > 0:\n",
    "                    predicted_rating = weighted_sum / similarity_sum\n",
    "                    recommendations[movie] = predicted_rating\n",
    "        \n",
    "        return recommendations.sort_values(ascending=False).head(n_recommendations)\n",
    "    \n",
    "    def popularity_recommend(self, n_recommendations=10):\n",
    "        \"\"\"基于流行度的推荐\"\"\"\n",
    "        return ratings.sort_values('平均评分', ascending=False).head(n_recommendations)\n",
    "\n",
    "# 创建推荐器\n",
    "recommender = MovieRecommender(user_movie)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d34e070-4b24-4a16-aabb-6de012d86701",
   "metadata": {},
   "source": [
    "## 推荐结果显示"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "79019ed2-142f-40ce-956c-bd6e82b8898c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== 步骤6：推荐结果展示 ===\n",
      "\n",
      "--- 用户 1 的推荐结果 ---\n",
      "\n",
      "--- 用户 2 的推荐结果 ---\n",
      "\n",
      "--- 用户 3 的推荐结果 ---\n",
      "\n",
      "--- 用户 4 的推荐结果 ---\n",
      "\n",
      "--- 用户 5 的推荐结果 ---\n"
     ]
    }
   ],
   "source": [
    "print(\"\\n=== 步骤6：推荐结果展示 ===\")\n",
    "\n",
    "# 选择几个用户进行推荐示例\n",
    "sample_users = user_movie.index[:5] if len(user_movie) >= 5 else user_movie.index\n",
    "\n",
    "for user_id in sample_users:\n",
    "    print(f\"\\n--- 用户 {user_id} 的推荐结果 ---\")\n",
    "    \n",
    "    # 基于物品的推荐\n",
    "    item_recs = recommender.item_based_recommend(user_id, 5)\n",
    "    if len(item_recs) > 0:\n",
    "        print(\"基于物品的推荐:\")\n",
    "        for i, (movie, score) in enumerate(item_recs.items(), 1):\n",
    "            print(f\"  {i}. {movie} - 预测评分: {score:.2f}\")\n",
    "    \n",
    "    # 基于用户的推荐\n",
    "    user_recs = recommender.user_based_recommend(user_id, 5)\n",
    "    if len(user_recs) > 0:\n",
    "        print(\"基于用户的推荐:\")\n",
    "        for i, (movie, score) in enumerate(user_recs.items(), 1):\n",
    "            print(f\"  {i}. {movie} - 预测评分: {score:.2f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "655f756f-d714-458c-bdf0-85822a7d22db",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
